package main;

import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 
 * Representation of just one mesh/set of nodes.
 * 
 */
public class Block implements Comparable<Block> {

	/**
	 * Creates a new instance of the block.
	 * 
	 * @param node
	 *            one node for this block.
	 */
	public Block(Node node) {

		blockId = Block.getNextId();

		this.nodeSet = new TreeSet<Node>();
		this.nodeSet.add(node);
		node.setBlock(this);
	}

	/**
	 * Creates a new instance of the block.
	 * 
	 * @param setNode
	 *            set of nodes for this block.
	 * @param commit
	 *            true, if nodes should update their relationship to this block.
	 */
	public Block(SortedSet<Node> setNode, boolean commit) {
		write.lock();
		try {

			blockId = Block.getNextId();

			this.nodeSet = setNode;
			if (commit)
				commit();

			if (this.nodeSet.size() == 1)
				isStable = true;
			isTaken = false;

		} finally {
			write.unlock();
		}
	}

	/**
	 * Add a node to this block.
	 * 
	 * @param node
	 *            node to add to this block.
	 */
	public void addNode(Node node) {
		this.nodeSet.add(node);
		node.setBlock(this);
	}

	/**
	 * Id-pool of the block. Guarded by this.
	 */
	private static long id = -1;

	/**
	 * Gets a new free id for the block.
	 * 
	 * @return
	 */
	private static synchronized long getNextId() {
		id++;
		return id;
	}

	/**
	 * Id of this block. Just for terms of block comparison. Immutable, if once
	 * set.
	 */
	private long blockId;

	/**
	 * Set of nodes for this block. Guarded by rwLock.
	 */
	private SortedSet<Node> nodeSet;
	/**
	 * Set of post-blocks, that cannot split this block. Guarded by rwLockPost.
	 */
	private SortedSet<Block> irrelevantPostBlocks = new TreeSet<Block>();

	/**
	 * Indicates whether this block is splitted. This is Semaphore with capacity
	 * 1, i.e. not split. If the block is split, the capacity is 0.
	 */
	public Semaphore isSplitted = new Semaphore(1, true);
	/**
	 * Indicates that this block is stable and cannot be split at this time.
	 * Guarded by rwLock.
	 */
	private boolean isStable = false;
	/**
	 * Indicates that this block is taken for computation. Guarded by rwLock.
	 */
	private boolean isTaken = false;
	/**
	 * Indicates that somebody want this block to become unstable. Guarded by
	 * rwLock.
	 */
	private boolean wantDestable = false;

	private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(
			true);
	private final Lock read = rwLock.readLock();
	private final Lock write = rwLock.writeLock();
	private final ReentrantReadWriteLock rwLockPost = new ReentrantReadWriteLock(
			true);
	private final Lock readPost = rwLockPost.readLock();
	private final Lock writePost = rwLockPost.writeLock();

	/**
	 * Preset for this block. All arrows from preset end up in this block.
	 * 
	 * @return preset.
	 */
	public Block pre() {
		read.lock();
		try {
			SortedSet<Node> preset = new TreeSet<Node>();

			for (Node node : nodeSet) {
				for (Node candidate : node.getPredNodeSet())
					// if (!preset.contains(candidate))
					preset.add(candidate);
			}

			return new Block(preset, false);
		} finally {
			read.unlock();
		}
	}

	/**
	 * Intersection of the nodes of two blocks.
	 * 
	 * @param block
	 *            block with which to intersect.
	 * @return intersection of two blocks.
	 */
	public Block intersect(Block block) {
		read.lock();
		try {

			SortedSet<Node> intersect = new TreeSet<Node>(this.nodeSet);
			intersect.retainAll(block.nodeSet);

			return new Block(intersect, false);
		} finally {
			read.unlock();
		}
	}

	/**
	 * Difference of the nodes of two blocks. This block nodes minus other block
	 * nodes.
	 * 
	 * @param block
	 *            other block
	 * @return Difference of two blocks.
	 */
	public Block difference(Block block) {
		read.lock();
		try {
			SortedSet<Node> difference = new TreeSet<Node>(nodeSet);

			difference.removeAll(block.nodeSet);

			return new Block(difference, false);
		} finally {
			read.unlock();
		}
	}

	/**
	 * Returns true if this block contains this node.
	 * 
	 * @param node
	 *            node
	 * @return true if this block contains this node.
	 */
	public boolean containsNode(Node node) {
		read.lock();
		try {
			return this.nodeSet.contains(node);
		} finally {
			read.unlock();
		}
	}

	/**
	 * Returns the number of nodes in this block.
	 * 
	 * @return
	 */
	private int size() {
		return nodeSet.size();
	}

	/**
	 * Returns true if this block has no nodes.
	 * 
	 * @return true, if empty.
	 */
	public boolean isEmpty() {
		read.lock();
		try {
			return this.size() == 0;
		} finally {
			read.unlock();
		}
	}

	/**
	 * Updates the nodes relationship to this block.
	 */
	public void commit() {
		write.lock();
		try {
			for (Node node : nodeSet) {
				node.setBlock(this);
			}
		} finally {
			write.unlock();
		}
	}

	/**
	 * Returns a set of nodes for this block.
	 * 
	 * @return set of nodes for this block.
	 */
	public SortedSet<Node> getNodeSet() {
		read.lock();
		try {
			return nodeSet;
		} finally {
			read.unlock();
		}
	}

	/**
	 * Returns true, if this block is processed/ reserved by some thread.
	 * 
	 * @return true if this block is taken.
	 */
	public boolean isTaken() {
		read.lock();
		try {
			return this.isTaken;
		} finally {
			read.unlock();
		}
	}

	/**
	 * Tries to take/reserve this block.
	 * 
	 * @return true, if this block is successfully taken.
	 */
	public boolean tryTake() {
		write.lock();
		try {
			if (isTaken || isStable)
				return false;
			isTaken = true;
			return true;
		} finally {
			write.unlock();
		}
	}

	/**
	 * Marks this block as unstable.
	 */
	public void destable() {
		write.lock();
		try {
			if (this.nodeSet.size() < 2)
				this.isStable = true;
			else {

				if (isTaken) {
					this.wantDestable = true;
				} else {
					this.isStable = false;
				}
			}
		} finally {
			write.unlock();
		}
	}

	/**
	 * Returns true if this block is stable, that means cannot be split.
	 * 
	 * @return true, if the block is stable.
	 */
	public boolean isStable() {
		read.lock();
		try {
			return this.isStable;
		} finally {
			read.unlock();
		}
	}

	/**
	 * Sets stable state of this block. ONLY for sequential use.
	 * 
	 * @param isStable
	 *            state
	 */
	public void setStable(boolean isStable) {
		this.isStable = isStable;
	}

	/**
	 * Sets this block to stable and releases this block.
	 */
	public void setStableAndRelease() {
		write.lock();
		try {
			this.isStable = true;
			release();
		} finally {
			write.unlock();
		}
	}

	/**
	 * Removes isTaken-flag, the block is therefore released by the thread for
	 * further procedures.
	 */
	public void release() {
		write.lock();
		try {
			if (wantDestable)
				this.isStable = false;
			wantDestable = false;
			this.isTaken = false;
		} finally {
			write.unlock();
		}
	}

	/**
	 * Gets the set of the irrelevant post-blocks. Those blocks cannot be used
	 * to split this block, so we do not need to check the split-condition on
	 * them.
	 * 
	 * @return set of irrelevant post-blocks.
	 */
	public SortedSet<Block> getIrrelevantPostBlocks() {
		readPost.lock();
		try {
			return irrelevantPostBlocks;
		} finally {
			readPost.unlock();
		}
	}

	/**
	 * Sets a new irrelevant post-block. Current block cannot be split by this
	 * block.
	 * 
	 * @param postBlock
	 *            irrelevant post-block.
	 */
	public void setIrrelevantPostBlock(Block postBlock) {
		writePost.lock();
		try {

			irrelevantPostBlocks.add(postBlock);
		} finally {
			writePost.unlock();
		}
	}

	/**
	 * Clears the irrelevant post-blocks to free the memory.
	 */
	public void clearIrrelevantPostBlocks() {
		writePost.lock();
		try {
			irrelevantPostBlocks.clear();
		} finally {
			writePost.unlock();
		}
	}

	/**
	 * Sets irrelevant postBlocks. ONLY for sequential use.
	 * 
	 * @param postBlocks
	 *            set of post-blocks.
	 */
	public void setIrrelevantPostBlocks(List<Block> postBlocks) {
		irrelevantPostBlocks.addAll(postBlocks);
	}

	/**
	 * String representation of this block.
	 */
	public String toString() {
		read.lock();
		try {
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.append("[");
			if (nodeSet.size() > 0) {
				stringBuilder.append(this.nodeSet.first().getCapacity()
						+ " -> ");
				//stringBuilder.append(this.nodeSet.first().getId());
			}
			for (Node node : nodeSet/*.tailSet(nodeSet.first())*/)
				stringBuilder.append(" " + node.getId());
			stringBuilder.append("]");
			return stringBuilder.toString();
		} finally {
			read.unlock();
		}
	}

	/**
	 * Compares blocks by blockId.
	 */
	@Override
	public int compareTo(Block block) {
		if (this.blockId < block.blockId)
			return -1;
		if (this.blockId > block.blockId)
			return 1;

		return 0;
	}
}
